package org.testis.testdriver.operations;

import org.testis.testdriver.*;
import org.testis.data.TestStepDataObject;
import org.testis.data.TestStepResultDataObject;
import org.testis.data.TestStepResultType;
import org.testis.services.TestStepExecutor;
import org.testis.testdriver.AtofEnumaration.AtofLogLevel;
import org.testis.testdriver.AtofEnumaration.DebuggingMode;
import org.testis.testdriver.conditions.ConditionEvaluatingService;
import org.testis.testdriver.translation.AtofValueTranslateService;
import org.testis.testdriver.translation.UnidentifiedTranslationException;
import org.testis.utils.UuidGenerator;
import com.mallardsoft.tuple.Pair;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Set;

/**
 * This class is the TestPartOperation Decorator to execute TestStep related 
 * operations. 
 * 
 * @author barisergun
 */
public class TestPartOperationExecuteTestStep extends TestPartOperationDecorator {

    private TestPart testPartInRun;
    private String m_DataMsgId;
    private TestDataObject curObj;
    private TestStep curStep;
    private String dataObjectLog;
    private Short caseDataIndex;
    private Integer noOfCurrentObject;
    private TestStepDataObject myData;

    public TestPartOperationExecuteTestStep(TestPart testPart) {
        testPartInRun = testPart;
        curStep = (TestStep) testPartInRun;
    }

    @Override
    public boolean runOperation(DebuggingMode mode) throws InterruptedException {
        if (curStep.getTestDataIterator() == null) {
            curStep.getStepResultSet().clear();
            // Only clear when the iterator is not set because
        }
        // Reset the Step result set. at the beginning.

        if (curStep.getTestDataList().isEmpty()) {

            Byte resByte = 2;
            StepContainer parentCont =
                    (StepContainer) curStep.getParent();

            String parentGuid = parentCont.getPartGuidLongFromChild();

            String currentGuid = curStep.getPartGuid()
                    + parentCont.getPartGuidLongFromChild().replaceAll(parentCont.getPartGuid(),
                    "");

            if (parentCont.getContainerType()
                    == AtofEnumaration.TestPartType.TestStepGroup) {
                StepContainer parentParent = (StepContainer) parentCont.getParent();
                parentGuid += parentParent.getPartGuidLongFromChild().replaceAll(
                        parentParent.getPartGuid(),
                        "");

                currentGuid += parentParent.getPartGuidLongFromChild().replaceAll(
                        parentParent.getPartGuid(),
                        "");
            }

            String stepName = "[Test Step] " + curStep.getPartName();

            NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                    Integer.parseInt(
                    curStep.getPartId()),
                    AtofEnumaration.TestPartType.TestStep.ordinal(),
                    resByte,
                    parentGuid,
                    stepName,
                    currentGuid,
                    ExecutionGuidProvider.createInstance().
                    getExistingExecId());

            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    curStep.getPartName(),
                    AtofLogLevel.INFO,
                    TestStringConstants.getEmptyTestStepString(),
                    currentGuid);
            return true;

        } else {
            m_DataMsgId = UuidGenerator.generateMsgId();
            iterateDataObjects(false);
            if (curObj != null) {

                if (super.runOperation(mode)) {

                    try {
                        prepareTestStepData(curObj,
                                m_DataMsgId);
                    } catch (UnidentifiedTranslationException eee) {

                        processTestResultTable(noOfCurrentObject,
                                caseDataIndex,
                                false,
                                myData);
                        Byte res = 0;
                        logResult(eee.getMessage(), AtofLogLevel.ERROR, res);
                        return false;

                    }

                    try {
                        ConditionEvaluatingService.evaluateConditions(curObj);
                        boolean stepResult = false;
                        if (curObj.getFailedConditions().isEmpty()) {
                            stepResult = runSpecificBehaviour();
                            TestStep ourStep = (TestStep) testPartInRun;
                            if (!ourStep.getAsynchCall() && ourStep.getDataArrayAppliesToCase()) {
                                int retryCount = 0;
                                AtofEnumaration.ExecutionInterventionType val = ExecutionInterventionStateSingleton.createInstance().
                                        getInterventionType();

                                while (!stepResult && retryCount < curStep.getRetryOnFailCount() && val
                                        != AtofEnumaration.ExecutionInterventionType.Abort) {
                                    iterateDataObjects(true);
                                    stepResult = runSpecificBehaviour();
                                    retryCount++;
                                    val = ExecutionInterventionStateSingleton.createInstance().
                                            getInterventionType();
                                }
                            }
                        } else { //if (curObj.getFailedConditions().isEmpty())
                            myData.setConditionFailed(true);
                            stepResult = true;
                            Byte res = 3;
                            logResult(TestStringConstants.getConditionFailedString()
                                    + "\n" + curObj.getFailedConditions().toString(), AtofLogLevel.INFO, res);

                            processTestResultTable(noOfCurrentObject,
                                    caseDataIndex,
                                    stepResult,
                                    myData);
                        }
                        testPartInRun.setTestPartResult(stepResult);
                        return stepResult;
                    } catch (Exception eee) {

                        Byte res = 0;
                        logResult(eee.getMessage(),
                                AtofLogLevel.DEBUG, res);
                        return false;

                    }


                } else {

                    processTestResultTable(noOfCurrentObject,
                            caseDataIndex,
                            false,
                            myData);
                    Byte res = 0;
                    logResult("Previous decorator operation failed",
                            AtofLogLevel.ERROR, res);
                    return false;

                }
            } else //if (curObj != null)
            {
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        curStep.getPartNameLongFromChild(),
                        AtofLogLevel.WARN,
                        TestStringConstants.getEmptyTestStepString(),
                        curStep.getPartGuidLongFromChild());
                testPartInRun.setTestPartResult(true);
                return true;
            }
        }

    }

    private void logResult(String logMessage, AtofLogLevel level, Byte resByte) {
        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                myData.getTestStepDataName(),
                level,
                logMessage,
                myData.getMessageId());


        String dataObjName = TestStringConstants.getDataObjectPreFixString()
                + myData.getTestStepDataName()
                + curStep.getTsgSpecificRetryCount();


        NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                Integer.parseInt(
                myData.getTestStepDataId()),
                AtofEnumaration.TestPartType.DataObject.ordinal(),
                resByte,
                curStep.getPartGuidLongFromChild(),
                dataObjName,
                myData.getMessageId(),
                ExecutionGuidProvider.createInstance().
                getExistingExecId());

        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                curStep.getPartNameLongFromChild(),
                AtofLogLevel.INFO,
                "Ended execution.",
                curStep.getPartGuidLongFromChild());
        //testPartInRun.setTestPartResult(false);

    }

    private void iterateDataObjects(boolean retrying) {


        boolean testStepHasData = curStep.getTestDataList().size() > 0;

        String retryString = "";

        if (!retrying) {
            if (curStep.getTestDataIterator() == null) {
                curStep.setTestDataIterator(curStep.getTestDataList().
                        listIterator());

            } else {
                if (curStep.getTestDataList().size() == 1) {
                    curStep.getTestDataIterator().previous();
                }
            }
            if (testStepHasData) {
                curObj = (TestDataObject) curStep.getTestDataIterator().next();
            }

        } else {
            curStep.getTestDataIterator().previous();
            curObj = (TestDataObject) curStep.getTestDataIterator().next();
            retryString = "Last attempt failed so retrying step.";
        }

        if (testStepHasData) {
            noOfCurrentObject = curStep.getTestDataList().indexOf(curObj)
                    + 1;
            caseDataIndex =
                    ((StepContainer) curStep.getParent()).getTestCaseDataIndex();

            dataObjectLog = "TestStepDataObject Name:"
                    + curObj.getDataObjectName() + " " + retryString;

            if (curStep.getDataArrayAppliesToCase()) {
                dataObjectLog += " TestCASE DataObject Number: "
                        + String.valueOf(caseDataIndex);
            } else // if (this.m_DataArrayAppliesToCase)
            {
                dataObjectLog += " TestSTEP DataObject Number: "
                        + String.valueOf(noOfCurrentObject);
            }
        } else //if(testStepHasData)
        {
            dataObjectLog = curStep.getPartName()
                    + " TestStep has no DataObject.";
        }

    }

    /**
     * This method is used to drive the specific Tasks that belong to this decorator
     * operation.
     * 
     * @return boolean the result of operation. This result will affect the operation
     * of linked decorator tasks. 
     */
    private boolean runSpecificBehaviour() throws
            InterruptedException {

        delayExecution(m_DataMsgId);

        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                curStep.getPartName(),
                AtofLogLevel.INFO,
                dataObjectLog,
                m_DataMsgId);

        TestStepExecutor executor = createExecutor();

        boolean sendSuccess = sendTestStepData(executor,
                myData);


        boolean stepResult = false;
        if (sendSuccess) {
            if (!myData.isAsynchCall()) {
                stepResult = getTestStepResult(myData,
                        curObj);

            } else {
                stepResult = false;
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        curStep.getPartName(),
                        AtofLogLevel.INFO,
                        "Asynchronous message result will be validated later.",
                        m_DataMsgId);
            }
            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    curStep.getPartNameLongFromChild(),
                    AtofLogLevel.INFO,
                    "Ended execution.",
                    curStep.getPartGuidLongFromChild());
        } else {
            Byte res = 0;
            logResult("Test Step sending failed",
                    AtofLogLevel.ERROR, res);

            if (!myData.isAsynchCall()) {

                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        curStep.getPartName(),
                        AtofLogLevel.INFO,
                        "Test Step Result: " + TestStepResultType.FAILURE
                        + "; Expected Result: " + myData.getExpectedResultType(),
                        myData.getMessageId());



            } else {
                myData.setStopExecution(true);
            }
        }

        processTestResultTable(noOfCurrentObject,
                caseDataIndex,
                stepResult,
                myData);

        delayAfterExecution(m_DataMsgId);

        return stepResult; // I will return the result it will have no use
        // if the parent Container type is TestCase. Because it's validation is
        // not done with this result.

    }

    /**
     * This method is used to delay the execution of the Test Step according to
     * m_DelayTime attribute.     
     */
    private void delayExecution(String msgId) throws InterruptedException {

        int delayDuration = curStep.getDelayTime();
        if (delayDuration != 0) {
            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    curStep.getPartName(),
                    AtofLogLevel.INFO,
                    "Delaying test step with: "
                    + String.valueOf(delayDuration)
                    + "seconds",
                    msgId);
            Thread.sleep(delayDuration * 1000);
        }

    }

    /**
     * This method is used to delay the execution of the Test Step according to
     * m_DelayTimeAfterExecute attribute after Execution
     */
    private void delayAfterExecution(String msgId) throws InterruptedException {

        int delayDuration = curStep.getDelayTimeAfterExecute();
        if (delayDuration != 0) {
            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    curStep.getPartName(),
                    AtofLogLevel.INFO,
                    "Delaying test step after execution with: "
                    + String.valueOf(delayDuration)
                    + "seconds",
                    msgId);
            Thread.sleep(delayDuration * 1000);
        }

    }

    /**
     * This method is used to prepare the Test Step Data that will be sent to the 
     * queue.
     * @param curObj The current TestDataObject)
     * @param msgId The msgId generated for the current transaction.
     * @return TestStepDataObject returns the prepared TestStepDataObject
     */
    private void prepareTestStepData(TestDataObject curObj,
            String msgId) {

        myData = new TestStepDataObject();
        myData.setStoreResultPostfix(curStep.getStoreResultPostfix());
        myData.setTimeOut(curStep.getTimeOut());
        myData.setMessageId(msgId);
        myData.setTestStepId(curStep.getPartId());
        myData.setModuleType(curStep.getModuleTypeName().toString());
        LinkedHashMap<String, String> cloneOfModuleParam =
                new LinkedHashMap<String, String>();
        cloneOfModuleParam.putAll(curStep.getModuleParams());
        processMapValues(cloneOfModuleParam);
        myData.setModuleDataToApply(cloneOfModuleParam);
        myData.setActionType(curStep.getActionName());
        myData.setMacAddress(curStep.getTestPcMacAddr());
        myData.setAsynchCall(curStep.getAsynchCall());
        myData.setTestStepDataName(curObj.getDataObjectName());
        myData.setTestStepDataId(curObj.getDataObjectId());
        LinkedHashMap<String, String> cloneOfMap =
                new LinkedHashMap<String, String>();
        cloneOfMap.putAll(curObj.getDataToApply());
        processMapValues(cloneOfMap);
        myData.setDataToApply(cloneOfMap);
        myData.setExpectedResultType(curObj.getExpectedRes());


    }

    private void processMapValues(LinkedHashMap<String, String> dataToApply) {
        Set<String> allKeys = dataToApply.keySet();
        Collection<String> allValues = dataToApply.values();
        int collSize = allValues.size();
        Iterator<String> valueIter =
                allValues.iterator();
        Iterator<String> keyIter =
                allKeys.iterator();
        for (int u = 0; u < collSize; u++) {
            String curValue = valueIter.next();
            String curKey = keyIter.next();
            AtofValueTranslateService service = new AtofValueTranslateService(
                    null,
                    null,
                    null,
                    TestStepGlobalDataHashMap.createInstance().getTeststepglobals());
            dataToApply.put(curKey,
                    service.translateRuntimeVariables(
                    curValue));

        }
    }

    /**
     * This method is used to create the executor that will be used to 
     * commmunicate with JMS.
     * 
     */
    private TestStepExecutor createExecutor() {
        TestStepExecutor executor = new TestStepExecutor();
        return executor;
    }

    /**
     * This method is used to send Test Step Data to the JMS Queue.
     * @param executor reference to the current TestStepExecutor object)
     * @param dataToSend reference to the Data Object that is prepared
     * @return boolean returns the result of the send operation.
     */
    private boolean sendTestStepData(TestStepExecutor executor,
            TestStepDataObject dataToSend) {

        return executor.send(dataToSend,
                true);
    }

    /**
     * This method is used to receive Test Step result.
     * @param executor reference to the current TestStepExecutor object)
     * @param dataToSend reference to the Data Object that is prepared.
     * @return boolean returns the result of the Test Step result.
     */
    private boolean getTestStepResult(TestStepDataObject dataToSend,
            TestDataObject curObj) {

        boolean stepResult = false;
        TestStepResultType stepResultType = null;
        TestStepResultDataObject result = TestStepExecutor.getResultReciever(
                true).recieve(
                dataToSend.getMessageId(),
                curStep.getTimeOut()
                * 1000);
        if (result == null) {

            stepResultType = waitTimeOutResult(dataToSend);
        } else {
            stepResultType = result.getResultType();
            LinkedHashMap<String, String> results = result.getReturnValueList();
            curObj.setResultMap(results);
            TestStepGlobalDataHashMap.createInstance().putAllKeyValues(results);


        }
        stepResult = dataToSend.getExpectedResultType() == stepResultType;

        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                curStep.getPartName(),
                AtofLogLevel.INFO,
                "Test Step Result: " + stepResultType
                + "; Expected Result: " + dataToSend.getExpectedResultType(),
                dataToSend.getMessageId());

        Byte resByte = 1;
        if (!stepResult) {
            resByte = 0;
        }

        String parentGuid = curStep.getPartGuidLongFromChild();

        if (curStep.getParent().getContainerType()
                == AtofEnumaration.TestPartType.TestStepGroup) {
            //The Test Step Guid is a bit different in this case
            // We must add the current  Test Case Guid Invocation Index and CaseData Index
            parentGuid = parentGuid + curStep.getParent().getParent().
                    getPartGuidLongFromChild().replaceAll(curStep.getParent().getParent().
                    getPartGuid(),
                    "");
        }

        String dataObjName = TestStringConstants.getDataObjectPreFixString()
                + dataToSend.getTestStepDataName()
                + curStep.getTsgSpecificRetryCount();

        NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                Integer.parseInt(
                dataToSend.getTestStepDataId()),
                AtofEnumaration.TestPartType.DataObject.ordinal(),
                resByte,
                parentGuid,
                dataObjName,
                dataToSend.getMessageId(),
                ExecutionGuidProvider.createInstance().
                getExistingExecId());


        return stepResult;



    }

    /**
     * This method is used to terminate the related Test Step processes if
     * the Test Step result was timed out. There is still a chance to receive
     * a Test Step result in this stage.
     * @param executor reference to the current TestStepExecutor object)
     * @param dataToSend reference to the Data Object that is prepared.
     * @return boolean returns the result of the Test Step result if successfull.
     */
    private TestStepResultType waitTimeOutResult(TestStepDataObject dataToSend) {
        TestStepResultType stepResult = TestStepResultType.NULL;
        TestStepResultDataObject result = null;
        dataToSend.setStopExecution(true);
        TestStepExecutor executor = new TestStepExecutor();
        executor.send(dataToSend,
                false);
        short tryCount = 0;
        while (result == null && tryCount < 6) {
            result = TestStepExecutor.getResultReciever(true).recieve(
                    dataToSend.getMessageId(),
                    5000);
            tryCount++;
        }
        dataToSend.setStopExecution(false);
        if (result == null) {
            stepResult = TestStepResultType.TIMEOUT_UNHANDLED;
        } else {

            if (result.isCancelled()) {
                stepResult = TestStepResultType.TIMEOUT;
            } else {
                stepResult = result.getResultType();
            }
        }
        return stepResult;
    }

    /**
     * This method is used to process the current Test Step result.
     * @param noOfCurrentObject current Test Data )
     * @param caseDataIndex message Id for the current transaction.
     * @param stepResult message Id for the current transaction.
     */
    private void processTestResultTable(int noOfCurrentObject,
            Short caseDataIndex,
            boolean stepResult,
            TestStepDataObject dataToSend) {

        String caseSuccessLog = TestStringConstants.getCaseSuccessLogString()
                + ((StepContainer) curStep.getParent()).getCaseSuccessPercentage();
        String stepSuccessLog = TestStringConstants.getStepSuccessLogString()
                + curStep.getM_Step_SuccessPercentage();

        Pair<TestStepDataObject, Boolean> resPair =
                new Pair<TestStepDataObject, Boolean>(dataToSend,
                stepResult);

        Integer cDataInd = caseDataIndex + 1;

        if (curStep.getDataArrayAppliesToCase()) {
            curStep.getCaseResultSet().put(cDataInd.shortValue(),
                    resPair);
            if (!curStep.getAsynchCall()) {
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        curStep.getPartName(),
                        AtofLogLevel.INFO,
                        "TestSteps current TestCase specific result set: "
                        + curStep.getCaseResultSet()
                        + caseSuccessLog,
                        dataToSend.getMessageId());
            }

        } else { //if (this.m_DataArrayAppliesToCase)


            curStep.getStepResultSet().put(noOfCurrentObject,
                    resPair);

            if (!curStep.getAsynchCall()) {
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        curStep.getPartName(),
                        AtofLogLevel.INFO,
                        "TestSteps current TestStep specific result set: "
                        + curStep.getStepResultSet()
                        + stepSuccessLog,
                        dataToSend.getMessageId());
            }



        }

    }
}
